﻿using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Internal.KuiHuaBaoDian.Services.Lockstep;
using KuiHuaBaoDian.Services.Lockstep;
using UnityEngine.Assertions;
using UnityEngine.Pool;

namespace KuiHuaBaoDian.Services.Lockstep {

    /// <summary>
    /// 帧同步服务
    /// </summary>
    public interface ILockstepService {

        /// <summary>
        /// 同步服务停止代理
        /// </summary>
        public delegate void StoppedDelegate();

        /// <summary>
        /// 当同步服务停止
        /// </summary>
        event StoppedDelegate Stopped;

        /// <summary>
        /// 预定的每帧的时间长度
        /// </summary>
        F64 FrameLength { get; }

        /// <summary>
        /// 预定的每帧的时间长度
        /// </summary>
        float FrameLengthRaw { get; }

        /// <summary>
        /// 当前帧总数
        /// </summary>
        int FrameCount { get; }

        /// <summary>
        /// 时间缩放
        /// </summary>
        float TimeScale { get; set; }

        /// <summary>
        /// 是否已暂停
        /// </summary>
        bool IsPaused { get; set; }

        /// <summary>
        /// 累积呈现的帧数, 包括当前帧
        /// </summary>
        int AccumulatedFrame { get; }

        float AccumulatedTime { get; }

        /// <summary>
        /// 是否已启用
        /// </summary>
        bool IsEnabled { get; }

        /// <summary>
        /// 添加实体
        /// </summary>
        /// <param name="entity">实体</param>
        LockstepEntityHandle AttachEntity(ILockstepEntity entity);

        /// <summary>
        /// 移除实体
        /// </summary>
        /// <param name="handle">实体</param>
        void DetachEntity(LockstepEntityHandle handle);
    }

    /// <summary>
    /// 帧同步服务
    /// </summary>
    public static class LockstepService {

        /// <summary>
        /// 单例
        /// </summary>
        public static ILockstepService Instance => m_Instance ??= KHBD.TypeMap.Instantiate<ILockstepService>();
        private static ILockstepService m_Instance;
    }

    [Implement(typeof(ILockstepService))]
    internal sealed class KHBD_LockstepService : ILockstepService, ILockstepServiceInternal, IService {

        private record EntityRecord(LockstepEntityHandle Handle, ILockstepEntity Entity);

        private readonly struct Operate {

            public bool IsAddOrRemove { get; init; }

            public int Index { get; init; }
        }

        private readonly ObjectPool<Queue<ILockstepCommand>> m_CommandQueueObjectPool = new(() => new(), actionOnRelease: item => item.Clear(), collectionCheck: false);

        public event ILockstepService.StoppedDelegate Stopped;

        private readonly IDictionary<LockstepEntityHandle, EntityRecord> m_EntityDictionary = new Dictionary<LockstepEntityHandle, EntityRecord>();
        private readonly List<EntityRecord> m_EntityAddingList = new();
        private readonly List<LockstepEntityHandle> m_EntityRemovingList = new();
        private readonly Queue<Operate> m_OperateQueue = new();

        private readonly Queue<LockstepFrame> m_FrameQueue = new();

        // 逻辑帧数
        private int m_FrameCount = 0;
        public int FrameCount => m_FrameCount;

        private float m_AccumulatedTime;
        public float AccumulatedTime => m_AccumulatedTime;

        public int AccumulatedFrame => (int)(m_AccumulatedTime / FrameLengthRaw);

        private bool m_InLogicFrame = false;

        public bool IsEnabled { get; private set; }

        public float FrameLengthRaw { get; private set; }
        private bool m_HasFrameLength = false;
        private F64 m_FrameLength;
        public F64 FrameLength {
            get {
                if (!m_HasFrameLength) {
                    m_HasFrameLength = true;
                    m_FrameLength = (F64)FrameLengthRaw;
                }
                return m_FrameLength;
            }
        }

        private int m_TimeScaleChangingFrameIndex;
        private float m_TimeScaleChanging = 1f;
        private float m_TimeScale = 1f;
        public float TimeScale {
            get => m_TimeScale;
            set {
                m_TimeScaleChanging = value;
                m_TimeScaleChangingFrameIndex = m_FrameCount;
            }
        }

        //private int m_IsPausedChangingFrameIndex;
        //private bool m_IsPausedChanging = false;
        private bool m_IsPaused = false;
        public bool IsPaused {
            get => m_IsPaused;
            set {
                m_IsPaused = value;
                //m_IsPausedChanging = value;
                //m_IsPausedChangingFrameIndex = m_FrameCount;
                //UnityEngine.Debug.Log($"{nameof(m_IsPaused)}:{m_IsPaused}, {nameof(m_AccumulatedTime)}:{m_AccumulatedTime}");
            }
        }

        void ILockstepServiceInternal.Notify(ILockstepCommand command) {
            command.Execute();
        }

        void ILockstepServiceInternal.Start(float frameLength) => Start(frameLength);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void Start(float frameLength) {
            FrameLengthRaw = frameLength;
            m_HasFrameLength = false;
            m_TimeScale = 1f;
            m_TimeScaleChanging = 1f;
            m_TimeScaleChangingFrameIndex = -1;
            m_IsPaused = false;
            //m_IsPausedChanging = false;
            //m_IsPausedChangingFrameIndex = -1;
            m_FrameCount = 0;
            m_AccumulatedTime = 0;
            IsEnabled = true;
            m_InLogicFrame = false;
        }

        void ILockstepServiceInternal.Stop() => Stop();
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void Stop() {
            IsEnabled = false;
            m_InLogicFrame = false;
            m_EntityDictionary.Clear();
            m_EntityAddingList.Clear();
            m_EntityRemovingList.Clear();
            m_FrameQueue.Clear();
            Stopped?.Invoke();
        }

        LockstepEntityHandle ILockstepServiceInternal.AttachEntity(ILockstepEntity entity) => DoAttachEntity(entity);
        public LockstepEntityHandle AttachEntity(ILockstepEntity entity) {
            Assert.IsNotNull(entity);
            Assert.IsTrue(KHBD.Context.IsApplicationPlaying && m_InLogicFrame, $"{nameof(LockstepService)}.{nameof(AttachEntity)} 只能运行于逻辑帧阶段");
            return DoAttachEntity(entity);
        }

        private LockstepEntityHandle DoAttachEntity(ILockstepEntity entity) {
            var handle = new LockstepEntityHandle { GUID = Guid.NewGuid() };
            var record = new EntityRecord(handle, entity);

            m_OperateQueue.Enqueue(new Operate { IsAddOrRemove = true, Index = m_EntityAddingList.Count });
            m_EntityAddingList.Add(record);
            return handle;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void AddEntity(LockstepEntityHandle handle, EntityRecord record) {
            m_EntityDictionary.Add(handle, record);
            record.Entity.OnAttached(m_FrameCount);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void RemoveEntity(LockstepEntityHandle handle) {
            if (m_EntityDictionary.TryGetValue(handle, out var record)) {
                m_EntityDictionary.Remove(handle);
                record.Entity.OnDetached();
            }
        }

        void ILockstepServiceInternal.DetachEntity(LockstepEntityHandle handle) => DoDetachEntity(handle);
        public void DetachEntity(LockstepEntityHandle handle) {
            Assert.IsTrue(KHBD.Context.IsApplicationPlaying && m_InLogicFrame, $"{nameof(LockstepService)}.{nameof(DetachEntity)} 只能运行于逻辑帧阶段");
            DoDetachEntity(handle);
        }

        private void DoDetachEntity(LockstepEntityHandle handle) {
            m_OperateQueue.Enqueue(new Operate { IsAddOrRemove = false, Index = m_EntityRemovingList.Count });
            m_EntityRemovingList.Add(handle);
        }

        void ILockstepServiceInternal.EnqueueFrame(LockstepFrame frame) {
            m_FrameQueue.Enqueue(frame);
        }

        void IService.OnApplicationQuit() {
            Stop();
        }

        void ILockstepServiceInternal.Step() {
            if (!IsEnabled) {
                return;
            }

            var lastFrameCount = m_FrameCount;

            LogicFrame();

            if (m_FrameCount <= 0) {
                return;
            }

            var isPaused = m_IsPaused ? 0f : 1f;
            var timeScale = TimeScale * isPaused;
            float deltaTime = UnityEngine.Time.deltaTime * timeScale;
            if (!m_IsPaused) {
                var accumulatedTime = m_AccumulatedTime + deltaTime;
                var frameTakedTime = m_FrameCount * FrameLengthRaw;
                var timeSpan = frameTakedTime - accumulatedTime;
                var frameLength = FrameLengthRaw * timeScale;
                var factor = timeSpan / frameLength;
                if (factor is >= 0f and <= 1f) {
                    factor = 1f;
                } else if (factor < 0f) {
                    factor += 1f;

                }
                deltaTime *= factor;
                m_AccumulatedTime += deltaTime;
                timeScale *= factor;
            }
            var syncedFrameCount = m_FrameCount - lastFrameCount;
            //Debug.Log($"{nameof(deltaTime)}={deltaTime}, {nameof(factor)}={factor}, {nameof(timeSpan)}={timeSpan}, {nameof(accumulatedTime)}={accumulatedTime}, {nameof(frameTakedTime)}={frameTakedTime}");
            UpdateStep(syncedFrameCount, deltaTime, timeScale);
            if (m_TimeScaleChangingFrameIndex >= 0 && m_AccumulatedTime >= m_TimeScaleChangingFrameIndex * FrameLengthRaw) {
                m_TimeScale = m_TimeScaleChanging;
                m_TimeScaleChangingFrameIndex = -1;
            }
            //if (m_IsPausedChangingFrameIndex >= 0 && m_AccumulatedTime >= m_IsPausedChangingFrameIndex * FrameLengthRaw) {
            //    UnityEngine.Debug.Log($"{nameof(m_IsPaused)}:{m_IsPaused}, {nameof(m_IsPausedChanging)}:{m_IsPausedChanging}, {nameof(m_AccumulatedTime)}:{m_AccumulatedTime}, {nameof(m_IsPausedChangingFrameIndex)}:{m_IsPausedChangingFrameIndex}");
            //    m_IsPaused = m_IsPausedChanging;
            //    m_IsPausedChangingFrameIndex = -1;
            //}
        }

        private void LogicFrame() {
            m_InLogicFrame = true;
            while (m_FrameQueue.Count > 0 && KHBD.Context.IsApplicationPlaying) {
                Queue<ILockstepCommand> endFrameQueue = null;
                var frame = m_FrameQueue.Dequeue();
                if (frame.Commands != null) {
                    foreach (var command in frame.Commands) {
                        //UnityEngine.Debug.Log($"{command.GetType()}:{frame.Index}:{m_FrameCount}");
                        if (command is ILockstepCommand.ISetTimeScale or ILockstepCommand.IPause) {
                            endFrameQueue ??= m_CommandQueueObjectPool.Get();
                            endFrameQueue.Enqueue(command);
                        } else if (command is ILockstepCommand.IResume) {
                            m_AccumulatedTime += LockstepService.Instance.FrameLengthRaw;
                            command.Execute();
                        } else {
                            command.Execute();
                        }
                        //UnityEngine.Debug.Log($"{nameof(m_IsPaused)}:{m_IsPaused}, {nameof(m_AccumulatedTime)}:{m_AccumulatedTime}");
                    }
                }
                foreach (var kv in m_EntityDictionary) {
                    var record = kv.Value;
                    var entity = record.Entity;
                    entity.DoLogicFrame(m_FrameCount);
                }

                while (m_OperateQueue.Count > 0 && KHBD.Context.IsApplicationPlaying) {
                    var operate = m_OperateQueue.Dequeue();
                    if (operate.IsAddOrRemove) {
                        var adding = m_EntityAddingList[operate.Index];
                        AddEntity(adding.Handle, adding);
                        adding.Entity.DoLogicFrame(m_FrameCount);
                    } else {
                        var removing = m_EntityRemovingList[operate.Index];
                        RemoveEntity(removing);
                    }
                }
                m_EntityAddingList.Clear();
                m_EntityRemovingList.Clear();

                while (endFrameQueue?.Count > 0 && KHBD.Context.IsApplicationPlaying) {
                    var command = endFrameQueue.Dequeue();
                    command.Execute();
                }
                if (endFrameQueue != null) {
                    m_CommandQueueObjectPool.Release(endFrameQueue);
                }
                m_FrameCount++;
            }
            m_InLogicFrame = false;
        }

        private void UpdateStep(int syncedFrameCount, float deltaTime, float timeScale) {
            //UnityEngine.Debug.Log($"{nameof(syncedFrameCount)}:{syncedFrameCount}, {nameof(deltaTime)}:{deltaTime}, {nameof(timeScale)}:{timeScale}");
            foreach (var kv in m_EntityDictionary) {
                var record = kv.Value;
                var entity = record.Entity;
                entity.DoUpdate(m_FrameCount, syncedFrameCount, deltaTime, timeScale);
            }
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Lockstep {

    public interface ILockstepServiceInternal {

        void EnqueueFrame(LockstepFrame frame);

        void Notify(ILockstepCommand command);

        void Step();

        void Start(float frameLength);

        void Stop();

        LockstepEntityHandle AttachEntity(ILockstepEntity entity);

        void DetachEntity(LockstepEntityHandle handle);
    }
}